// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/startup_task_runner.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task_runner.h"

#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {
namespace {

    using base::Closure;
    using testing::_;
    using testing::Assign;
    using testing::Invoke;
    using testing::WithArg;

    int observer_calls = 0;
    int task_count = 0;
    int observer_result;
    base::Closure task;

    // I couldn't get gMock's SaveArg to compile, hence had to save the argument
    // this way
    bool SaveTaskArg(const Closure& arg)
    {
        task = arg;
        return true;
    }

    void Observer(int result)
    {
        observer_calls++;
        observer_result = result;
    }

    class StartupTaskRunnerTest : public testing::Test {
    public:
        void SetUp() override
        {
            last_task_ = 0;
            observer_calls = 0;
            task_count = 0;
        }

        int Task1()
        {
            last_task_ = 1;
            task_count++;
            return 0;
        }

        int Task2()
        {
            last_task_ = 2;
            task_count++;
            return 0;
        }

        int FailingTask()
        {
            // Task returning failure
            last_task_ = 3;
            task_count++;
            return 1;
        }

        int GetLastTask() { return last_task_; }

    private:
        int last_task_;
    };

    // We can't use the real message loop, even if we want to, since doing so on
    // Android requires a complex Java infrastructure. The test would have to built
    // as a content_shell test; but content_shell startup invokes the class we are
    // trying to test.
    //
    // The mocks are not directly in TaskRunnerProxy because reference counted
    // objects seem to confuse the mocking framework

    class MockTaskRunner {
    public:
        MOCK_METHOD3(
            PostDelayedTask,
            bool(const tracked_objects::Location&, const Closure&, base::TimeDelta));
        MOCK_METHOD3(
            PostNonNestableDelayedTask,
            bool(const tracked_objects::Location&, const Closure&, base::TimeDelta));
    };

    class TaskRunnerProxy : public base::SingleThreadTaskRunner {
    public:
        TaskRunnerProxy(MockTaskRunner* mock)
            : mock_(mock)
        {
        }
        bool RunsTasksOnCurrentThread() const override { return true; }
        bool PostDelayedTask(const tracked_objects::Location& location,
            const Closure& closure,
            base::TimeDelta delta) override
        {
            return mock_->PostDelayedTask(location, closure, delta);
        }
        bool PostNonNestableDelayedTask(const tracked_objects::Location& location,
            const Closure& closure,
            base::TimeDelta delta) override
        {
            return mock_->PostNonNestableDelayedTask(location, closure, delta);
        }

    private:
        MockTaskRunner* mock_;
        ~TaskRunnerProxy() override { }
    };

    TEST_F(StartupTaskRunnerTest, SynchronousExecution)
    {
        MockTaskRunner mock_runner;
        scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);

        EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
        EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0);

        StartupTaskRunner runner(base::Bind(&Observer), proxy);

        StartupTask task1 = base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this));
        runner.AddTask(task1);
        EXPECT_EQ(GetLastTask(), 0);
        StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
        runner.AddTask(task2);

        // Nothing should run until we tell them to.
        EXPECT_EQ(GetLastTask(), 0);
        runner.RunAllTasksNow();

        // On an immediate StartupTaskRunner the tasks should now all have run.
        EXPECT_EQ(GetLastTask(), 2);

        EXPECT_EQ(task_count, 2);
        EXPECT_EQ(observer_calls, 1);
        EXPECT_EQ(observer_result, 0);

        // Running the tasks asynchronously shouldn't do anything
        // In particular Post... should not be called
        runner.StartRunningTasksAsync();

        // No more tasks should be run and the observer should not have been called
        // again
        EXPECT_EQ(task_count, 2);
        EXPECT_EQ(observer_calls, 1);
    }

    TEST_F(StartupTaskRunnerTest, NullObserver)
    {
        MockTaskRunner mock_runner;
        scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);

        EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
        EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0);

        StartupTaskRunner runner(base::Callback<void(int)>(), proxy);

        StartupTask task1 = base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this));
        runner.AddTask(task1);
        EXPECT_EQ(GetLastTask(), 0);
        StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
        runner.AddTask(task2);

        // Nothing should run until we tell them to.
        EXPECT_EQ(GetLastTask(), 0);
        runner.RunAllTasksNow();

        // On an immediate StartupTaskRunner the tasks should now all have run.
        EXPECT_EQ(GetLastTask(), 2);
        EXPECT_EQ(task_count, 2);

        // Running the tasks asynchronously shouldn't do anything
        // In particular Post... should not be called
        runner.StartRunningTasksAsync();

        // No more tasks should have been run
        EXPECT_EQ(task_count, 2);

        EXPECT_EQ(observer_calls, 0);
    }

    TEST_F(StartupTaskRunnerTest, SynchronousExecutionFailedTask)
    {
        MockTaskRunner mock_runner;
        scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);

        EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
        EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0);

        StartupTaskRunner runner(base::Bind(&Observer), proxy);

        StartupTask task3 = base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this));
        runner.AddTask(task3);
        EXPECT_EQ(GetLastTask(), 0);
        StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
        runner.AddTask(task2);

        // Nothing should run until we tell them to.
        EXPECT_EQ(GetLastTask(), 0);
        runner.RunAllTasksNow();

        // Only the first task should have run, since it failed
        EXPECT_EQ(GetLastTask(), 3);
        EXPECT_EQ(task_count, 1);
        EXPECT_EQ(observer_calls, 1);
        EXPECT_EQ(observer_result, 1);

        // After a failed task all remaining tasks should be cancelled
        // In particular Post... should not be called by running asynchronously
        runner.StartRunningTasksAsync();

        // The observer should only be called the first time the queue completes and
        // no more tasks should have run
        EXPECT_EQ(observer_calls, 1);
        EXPECT_EQ(task_count, 1);
    }

    TEST_F(StartupTaskRunnerTest, AsynchronousExecution)
    {

        MockTaskRunner mock_runner;
        scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);

        EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
        EXPECT_CALL(
            mock_runner,
            PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0)))
            .Times(testing::Between(2, 3))
            .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg)));

        StartupTaskRunner runner(base::Bind(&Observer), proxy);

        StartupTask task1 = base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this));
        runner.AddTask(task1);
        StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
        runner.AddTask(task2);

        // Nothing should run until we tell them to.
        EXPECT_EQ(GetLastTask(), 0);
        runner.StartRunningTasksAsync();

        // No tasks should have run yet, since we the message loop hasn't run.
        EXPECT_EQ(GetLastTask(), 0);

        // Fake the actual message loop. Each time a task is run a new task should
        // be added to the queue, hence updating "task". The loop should actually run
        // at most 3 times (once for each task plus possibly once for the observer),
        // the "4" is a backstop.
        for (int i = 0; i < 4 && observer_calls == 0; i++) {
            task.Run();
            EXPECT_EQ(i + 1, GetLastTask());
        }
        EXPECT_EQ(task_count, 2);
        EXPECT_EQ(observer_calls, 1);
        EXPECT_EQ(observer_result, 0);

        // Check that running synchronously now doesn't do anything

        runner.RunAllTasksNow();
        EXPECT_EQ(task_count, 2);
        EXPECT_EQ(observer_calls, 1);
    }

    TEST_F(StartupTaskRunnerTest, AsynchronousExecutionFailedTask)
    {

        MockTaskRunner mock_runner;
        scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner);

        EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0);
        EXPECT_CALL(
            mock_runner,
            PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0)))
            .Times(testing::Between(1, 2))
            .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg)));

        StartupTaskRunner runner(base::Bind(&Observer), proxy);

        StartupTask task3 = base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this));
        runner.AddTask(task3);
        StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this));
        runner.AddTask(task2);

        // Nothing should run until we tell them to.
        EXPECT_EQ(GetLastTask(), 0);
        runner.StartRunningTasksAsync();

        // No tasks should have run yet, since we the message loop hasn't run.
        EXPECT_EQ(GetLastTask(), 0);

        // Fake the actual message loop. Each time a task is run a new task should
        // be added to the queue, hence updating "task". The loop should actually run
        // at most twice (once for the failed task plus possibly once for the
        // observer), the "4" is a backstop.
        for (int i = 0; i < 4 && observer_calls == 0; i++) {
            task.Run();
        }
        EXPECT_EQ(GetLastTask(), 3);
        EXPECT_EQ(task_count, 1);

        EXPECT_EQ(observer_calls, 1);
        EXPECT_EQ(observer_result, 1);

        // Check that running synchronously now doesn't do anything
        runner.RunAllTasksNow();
        EXPECT_EQ(observer_calls, 1);
        EXPECT_EQ(task_count, 1);
    }
} // namespace
} // namespace content
